/*
 * File    : TorrentImpl.java
 * Created : 08-Dec-2003
 * By      : parg
 * 
 * Azureus - a Java Bittorrent client
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.gudy.azureus2.pluginsimpl.local.torrent;

import java.io.File;
import java.net.URL;

import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.download.DownloadManagerStateFactory;
import org.gudy.azureus2.core3.internat.LocaleTorrentUtil;
import org.gudy.azureus2.core3.internat.LocaleUtilDecoder;
import org.gudy.azureus2.core3.internat.LocaleUtilEncodingException;
import org.gudy.azureus2.core3.logging.LogRelation;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.torrent.TOTorrentFactory;
import org.gudy.azureus2.core3.torrent.TOTorrentFile;
import org.gudy.azureus2.core3.util.BEncoder;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.FileUtil;
import org.gudy.azureus2.core3.util.TorrentUtils;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.download.DownloadException;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.plugins.torrent.TorrentAnnounceURLList;
import org.gudy.azureus2.plugins.torrent.TorrentEncodingException;
import org.gudy.azureus2.plugins.torrent.TorrentException;
import org.gudy.azureus2.plugins.torrent.TorrentFile;
import org.gudy.azureus2.pluginsimpl.local.download.DownloadImpl;
import org.gudy.azureus2.pluginsimpl.local.download.DownloadManagerImpl;
import org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl;

import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.plugins.magnet.MagnetPlugin;
/**
 * @author parg
 *
 */
import java.util.Map;

public class TorrentImpl extends LogRelation implements Torrent {
    private static MagnetPlugin magnet_plugin;

    private PluginInterface pi;
    private TOTorrent torrent;
    private LocaleUtilDecoder decoder;

    private boolean complete;

    public TorrentImpl(TOTorrent _torrent) {
        this(null, _torrent);
    }

    public TorrentImpl(PluginInterface _pi, TOTorrent _torrent) {
        pi = _pi;
        torrent = _torrent;
    }

    public String getName() {
        String utf8Name = torrent.getUTF8Name();
        String name = utf8Name == null ? decode(torrent.getName()) : utf8Name;

        name = FileUtil.convertOSSpecificChars(name, false);

        return (name);
    }

    public URL getAnnounceURL() {
        return (torrent.getAnnounceURL());
    }

    public void setAnnounceURL(URL url) {
        torrent.setAnnounceURL(url);

        updated();
    }

    public TorrentAnnounceURLList getAnnounceURLList() {
        return (new TorrentAnnounceURLListImpl(this));
    }

    public TOTorrent getTorrent() {
        return (torrent);
    }

    public boolean isDecentralised() {
        return (TorrentUtils.isDecentralised(torrent));
    }

    public boolean isDecentralisedBackupEnabled() {
        return (TorrentUtils.getDHTBackupEnabled(torrent));
    }

    public void setDecentralisedBackupRequested(boolean requested) {
        TorrentUtils.setDHTBackupRequested(torrent, requested);
    }

    public boolean isDecentralisedBackupRequested() {
        return (TorrentUtils.isDHTBackupRequested(torrent));

    }

    public boolean isPrivate() {
        return (TorrentUtils.getPrivate(torrent));
    }

    public void setPrivate(boolean priv) {
        TorrentUtils.setPrivate(torrent, priv);
    }

    public boolean wasCreatedByUs() {
        return (TorrentUtils.isCreatedTorrent(torrent));
    }

    public URL getMagnetURI()

    throws TorrentException {
        if (magnet_plugin == null) {

            PluginInterface magnet_pi = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByClass(MagnetPlugin.class);

            if (magnet_pi != null) {

                magnet_plugin = (MagnetPlugin) magnet_pi.getPlugin();
            }
        }

        if (magnet_plugin == null) {

            throw (new TorrentException("MegnetPlugin unavailable"));
        }

        try {
            URL res = magnet_plugin.getMagnetURL(torrent.getHash());

            return (res);

        } catch (TOTorrentException e) {

            throw (new TorrentException(e));
        }
    }

    public byte[] getHash() {
        try {
            return (torrent.getHash());

        } catch (TOTorrentException e) {

            Debug.printStackTrace(e);

            return (null);
        }
    }

    public long getSize() {
        return (torrent.getSize());
    }

    public String getComment() {
        return (decode(torrent.getComment()));
    }

    public void setComment(String comment) {
        torrent.setComment(comment);
    }

    public long getCreationDate() {
        return (torrent.getCreationDate());
    }

    public String getCreatedBy() {
        return (decode(torrent.getCreatedBy()));
    }

    public long getPieceSize() {
        return (torrent.getPieceLength());
    }

    public long getPieceCount() {
        return (torrent.getNumberOfPieces());
    }

    public byte[][] getPieces() {
        try {
            return (torrent.getPieces());

        } catch (TOTorrentException e) {

            Debug.printStackTrace(e);

            return (new byte[0][0]);
        }
    }

    public TorrentFile[] getFiles() {
        TOTorrentFile[] files = torrent.getFiles();

        TorrentFile[] res = new TorrentFile[files.length];

        for (int i = 0; i < res.length; i++) {

            TOTorrentFile tf = files[i];

            byte[][] comps = tf.getPathComponents();

            String name = "";

            for (int j = 0; j < comps.length; j++) {

                String comp = decode(comps[j]);

                comp = FileUtil.convertOSSpecificChars(comp, j != comps.length - 1);

                name += (j == 0 ? "" : File.separator) + comp;
            }

            res[i] = new TorrentFileImpl(name, tf.getLength());
        }

        return (res);
    }

    protected void getDecoder() {
        // We defer the getting of the decoder until it is required as this Torrent may have been
        // created in order to simply remove additional properties from it before serialising it
        // Indeed, this was happening and unfortunately resulting in 1) the encoding being
        // serialised 2) the user being prompted for an encoding choice

        try {
            decoder = LocaleTorrentUtil.getTorrentEncoding(torrent);

        } catch (Throwable e) {

        }
    }

    public String getEncoding() {
        getDecoder();

        if (decoder != null) {

            return (decoder.getName());
        }

        return (Constants.DEFAULT_ENCODING);
    }

    public void setEncoding(String encoding) throws TorrentEncodingException {
        try {
            LocaleTorrentUtil.setTorrentEncoding(torrent, encoding);
        } catch (LocaleUtilEncodingException e) {
            throw new TorrentEncodingException("Failed to set the encoding", e);
        }
    }

    public void setDefaultEncoding() throws TorrentEncodingException {
        setEncoding(Constants.DEFAULT_ENCODING);
    }

    protected String decode(byte[] data) {
        getDecoder();

        if (data != null) {

            if (decoder != null) {

                try {
                    return (decoder.decodeString(data));

                } catch (Throwable e) {
                }
            }

            return (new String(data));
        }

        return ("");
    }

    public Object getAdditionalProperty(String name) {
        return (torrent.getAdditionalProperty(name));
    }

    public Torrent removeAdditionalProperties() {
        try {
            TOTorrent t = TOTorrentFactory.deserialiseFromMap(torrent.serialiseToMap());

            t.removeAdditionalProperties();

            return (new TorrentImpl(t));

        } catch (TOTorrentException e) {

            Debug.printStackTrace(e);

            return (this);
        }
    }

    public void setPluginStringProperty(String name, String value) {
        PluginInterface p = pi;

        if (p == null) {

            p = UtilitiesImpl.getPluginThreadContext();
        }

        if (p == null) {

            name = "<internal>." + name;

        } else {

            name = p.getPluginID() + "." + name;
        }

        TorrentUtils.setPluginStringProperty(torrent, name, value);
    }

    public String getPluginStringProperty(String name) {
        PluginInterface p = pi;

        if (p == null) {

            p = UtilitiesImpl.getPluginThreadContext();
        }

        if (p == null) {

            name = "<internal>." + name;

        } else {

            name = p.getPluginID() + "." + name;
        }

        return (TorrentUtils.getPluginStringProperty(torrent, name));
    }

    public void setMapProperty(String name, Map value) {
        TorrentUtils.setPluginMapProperty(torrent, name, value);
    }

    public Map getMapProperty(String name) {
        return (TorrentUtils.getPluginMapProperty(torrent, name));
    }

    public Map writeToMap()

    throws TorrentException {
        try {
            return (torrent.serialiseToMap());

        } catch (TOTorrentException e) {

            throw (new TorrentException("Torrent::writeToMap: fails", e));
        }
    }

    public byte[] writeToBEncodedData()

    throws TorrentException {
        try {
            Map map = torrent.serialiseToMap();

            return (BEncoder.encode(map));

        } catch (Throwable e) {

            throw (new TorrentException("Torrent::writeToBEncodedData: fails", e));
        }
    }

    public void writeToFile(File file)

    throws TorrentException {
        try {
            // don't use TorrentUtils.writeToFile as this updates the internal torrent
            // file reference an means that the torrent get's auto-written to the new
            // location in future, most likley NOT the desired behaviour

            torrent.serialiseToBEncodedFile(file);

        } catch (TOTorrentException e) {

            throw (new TorrentException("Torrent::writeToFile: fails", e));
        }
    }

    protected void updated() {
        try {
            DownloadImpl dm = (DownloadImpl) DownloadManagerImpl.getDownloadStatic(torrent);

            if (dm != null) {

                dm.torrentChanged();
            }
        } catch (DownloadException e) {

            // torrent may not be running
        }
    }

    public void save() throws TorrentException {
        try {
            TorrentUtils.writeToFile(torrent);

        } catch (TOTorrentException e) {

            throw (new TorrentException("Torrent::save Fails", e));
        }
    }

    public void setComplete(File data_dir)

    throws TorrentException {
        try {
            LocaleTorrentUtil.setDefaultTorrentEncoding(torrent);

            DownloadManagerState download_manager_state = DownloadManagerStateFactory.getDownloadState(torrent);

            TorrentUtils.setResumeDataCompletelyValid(download_manager_state);

            download_manager_state.save();

            complete = true;

        } catch (Throwable e) {

            throw (new TorrentException("encoding selection fails", e));
        }
    }

    public boolean isComplete() {
        // TODO: could check the download state too I guess...

        return (complete);
    }

    // Pass LogRelation off to core objects

    /*
     * (non-Javadoc)
     * @see org.gudy.azureus2.core3.logging.LogRelation#getLogRelationText()
     */
    public String getRelationText() {
        return propogatedRelationText(torrent);
    }

    /*
     * (non-Javadoc)
     * @see org.gudy.azureus2.core3.logging.LogRelation#getQueryableInterfaces()
     */
    public Object[] getQueryableInterfaces() {
        return new Object[] { torrent };
    }

    public boolean isSimpleTorrent() {
        return torrent.isSimpleTorrent();
    }
}